<template>
	<view class="vi_stepper" :class="{'vi_stepper_borderMode':borderMode ,'vi_stepper_integrated':integrated}">
		<!-- 减按钮 -->
		<view :class="btnClass||''"
			@touchstart="touchStart($event, 'reduce')"
			@touchmove="touchMove($event, 'reduce')"
			@touchend="touchEnd($event, 'reduce')">
			<vi-style-controller
				compName="stepper"
				:size="setting.size"
				:type="setting.type"
				:color="color"
				:textColor="textColor"
				:radius="reduceR"
				:borderWidth="reduceBorderW"
				:plain="plain"
				:disabled="reduceLimit||disabled"
				:readOnly="readOnly"
				:fixBorder="integrated&&borderMode"
				borderBox
				inline
				active>
				<text class="iconfont" :class="iconSetting.reduce"></text>
			</vi-style-controller>
		</view>
		<!-- 输入框 -->
		<view class="vi_stepper_box" :class="boxClass||''">
			<vi-style-controller
				compName="stepper"
				hoverClass="none"
				:size="setting.size"
				:type="integrated&&borderMode?setting.type:''"
				:color="integrated&&borderMode?color:''"
				:basicBackground="boxBgColor"
				:borderColor="!integrated&&disabled?'transparent':''"
				textColor="inherit"
				plain
				:radius="0"
				:width="boxW"
				:borderWidth="boxBorderW"
				:disabled="disabled"
				:readOnly="readOnly"
				:fixBorder="integrated&&borderMode"
				borderBox
				inline
				active>
				<view class="vi_stepper_inputbox">
					<view :class="'vi_inputCell ' +(inputFocus?'vi_stepper_show':'vi_stepper_hide')" v-if="setting.useInput">
						<input class="input vi_stepper_input" type="number"
							:focus="inputFocus"
							:value="inValue"
							:disabled="disabled||readOnly"
							:maxlength="inputLen"
							@focus="actionFocus"
							@blur="actionBlur"
							@confirm="actionConfirm"
							@input="actionInput"
							/>
					</view>
					<view :class="'vi_inputCell ' +(!inputFocus?'vi_stepper_show':'vi_stepper_hide')" @click="openInput">
						{{inValue}}{{unit||""}}
					</view>
				</view>
			</vi-style-controller>
		</view>
		<!-- 加按钮 -->
		<view :class="btnClass||''"
			@touchstart="touchStart($event, 'add')"
			@touchmove="touchMove($event, 'add')"
			@touchend="touchEnd($event, 'add')">
			<vi-style-controller
				compName="stepper"
				:size="setting.size"
				:type="setting.type"
				:color="color"
				:textColor="textColor"
				:radius="addR"
				:borderWidth="addBorderW"
				:plain="plain"
				:disabled="addLimit||disabled"
				:readOnly="readOnly"
				:fixBorder="integrated&&borderMode"
				borderBox
				inline
				active>
				<text class="iconfont" :class="iconSetting.add"></text>
			</vi-style-controller>
		</view>
	</view>
</template>

<script>
	export default {
		name:"viStepper",
		model:{
			prop: "value",
			event: "input"
		},
		props:{//尺寸
			type:{
				type:[String,null],
				default:null
			},
			color:{
				type:[String,null],
				default:""
			},
			textColor:{
				type:[String,null],
				default:""
			},
			//按钮圆角，可选值square、circle，以及数值带单位
			radius:{
				type:[String,Number,null],
				default:null
			},
			size:{
				type:[String,null],
				default:null
			},
			//按钮镂空
			plain:{
				type:[Boolean,null],
				default:false
			},
			value:{
				type:[Number,null],
				default:0
			},
			//按钮与输入框一体式
			integrated:{
				type:[Boolean,null],
				default:false
			},
			//使用边框
			borderMode:{
				type:[Boolean,null],
				default:false
			},
			//输入框背景色
			boxBgColor:{
				type:[Boolean,String,null],
				default:null
			},
			//输入框宽度
			boxWidth:{
				type:[Number,null],
				default:0
			},
			//输入框自动宽度预留间隙，单位英文字符
			boxSpace:{
				type:[Number,null],
				default:null
			},
			//初始化时，零值是否显示
			zeroShow:{
				type:[Boolean,null],
				default:null
			},
			min:{
				type:[Number,null],
				default:null
			},
			max:{
				type:[Number,null],
				default:null
			},
			step:{
				type:[Number,null],
				default:null
			},
			//允许数值存在多少位小数，超出将四舍五入
			decimals:{
				type:[Number,null],
				default:0
			},
			//固定显示多少位小数，数值小数位不足则补0，超出则不处理。可以大于decimals
			fixDecimals:{
				type:[Number,null],
				default:0
			},
			//内置单位字符，显示在数值旁
			unit:{
				type:[String,null],
				default:""
			},
			//输入框最大输入长度
			maxlength:{
				type:[Number,null],
				default:0
			},
			btnClass:{
				type:[String,null],
				default:""
			},
			boxClass:{
				type:[String,null],
				default:""
			},
			useLongPress:{
				type:[Boolean,null],
				default:false
			},
			useInput:{
				type:[Boolean,null],
				default:null
			},
			disabled:{
				type:[Boolean,null],
				default:false
			},
			//只读，不可点击，只能系统更改
			readOnly:{
				type:[Boolean,null],
				default:false
			},
			icon:{
				type:[Object,null],
				default:()=>({})
			}
		},
		data() {
			return {
				squareRatio:0.2,
				inValue:"",
				inputFocus:false,		//输入组件激活，原生组件存在性能问题，快速增减时中间值可能跳过渲染。故只在激活时使用组件，未激活则用view代替显示
				oValue:0,				//暂存，用于输入无效字符后还原
				longPress:false,		//长按启动标志
				longPressTimer:null,	//长按判断计时器
				longPressDuration:500,	//长按启动时间
				increaseTimer:null,		//长按快增计时器
				increaseSpeed:100,		//快增间隔时间，小于110 input组件渲染掉帧
				dicIcon:{
					reduce:"icon-reduce",
					add:"icon-add"
				}
			}
		},
		computed:{
			//将默认值内含到css、计算属性等地方，只内含真值
			//规避特殊使用的情况下，由prop传入undefined、null等无效数据导致的，默认值失效问题。例如组件嵌套且需要组件间参数传递
			//HBuilder X3.3.13; 微信开发者工具stable 1.05.2111300
			//微信小程序会对传入的undefined、null值根据参数类型输出不同空值(false、""、null、0)，参数类型为null或未指定参数类型则输出null
			//H5会对传入的undefined取默认值，未设默认值则输出undefined。对传入的null值固定输出null
			//做参数传递时需警惕两者行为不同而造成的陷阱，主要问题出现在微信小程序的空值转化，输出非用户传入的空值、假值，使默认真值丢失
			//兼容空值取默认值的可行方案目前为，参数类型加入null如[Boolean,null]，判断传入参数是否为定义的类型，是则使用，否则赋予默认值
			setting(){
				return {
					type: this.$api.isDefined(this.type)? this.type :"primary",
					size: this.$api.isDefined(this.size)? this.size :"md",
					radius: this.$api.isDefined(this.radius)? this.radius :"circle",
					max: this.$api.isValue(this.max)? this.max :Infinity,
					min: this.$api.isValue(this.min)? this.min :0,
					step: this.$api.isValue(this.step)? this.step :1,
					useInput: this.$api.isBoolean(this.useInput)? this.useInput :true,
					boxSpace: this.$api.isValue(this.boxSpace)? this.boxSpace :1,
					zeroShow: this.$api.isBoolean(this.zeroShow)? this.zeroShow :true,
				}
			},
			maxDecimals(){
				//最大值、最小值、步阶、数值最大小数位、数值固定小数位，取其中最大值
				let stepLen = this.$api.lengthOf(this.setting.step, {type:"decimal"}),
					maxLen = this.setting.max==Infinity?0:this.$api.lengthOf(this.setting.max, {type:"decimal"}),
					minLen = this.$api.lengthOf(this.setting.min, {type:"decimal"});
				return Math.max(this.decimals||0, this.fixDecimals||0, stepLen, maxLen, minLen)
			},
			inputLen(){
				//如果设了输入长度则直接使用
				if(this.maxlength){
					return this.maxlength
				}
				//如果设置了最大值、最小值则根据最值和最大小数位计算
				let res = Math.max(this.setting.max==Infinity?6:String(parseInt(this.setting.max)).length, String(parseInt(this.setting.min)).length);
				//默认值3
				return (res>0?res:2) +this.maxDecimals +1
			},
			boxW(){
				//如果设了宽度则使用宽度
				if(this.boxWidth){
					return Math.round(this.boxWidth) /2 +'em'
				}
				let unitLen = this.unit? this.$api.lengthOf(this.unit, {CN2:true}) :0;
				//如果设置了最大值、最小值则根据最值和最大小数位计算
				let res = Math.max(this.setting.max==Infinity?0:String(parseInt(this.setting.max)).length, String(parseInt(this.setting.min)).length);
				//默认2em + boxSpace/2
				return ((res>0?res:2) +this.maxDecimals +this.setting.boxSpace +unitLen)/2 +1 +'em'
			},
			reduceBorderW(){
				if(this.integrated&&this.borderMode){
					return "1px 0 1px 1px"
				}
				return 1
			},
			addBorderW(){
				if(this.integrated&&this.borderMode){
					return "1px 1px 1px 0"
				}
				return 1
			},
			boxBorderW(){
				if(this.integrated&&this.borderMode){
					return "1px 0 1px 0"
				}
				return 1
			},
			reduceR(){
				let radius = this.setting.radius;
				if(radius=="circle"){
					radius = "50%"
				}else if(radius=="square"){
					radius = this.squareRatio *100 +"%"
				}
				if(this.integrated){
					return `${radius} 0 0 ${radius}`
				}
				return radius
			},
			addR(){
				let radius = this.setting.radius;
				if(radius=="circle"){
					radius = "50%"
				}else if(radius=="square"){
					radius = this.squareRatio *100 +"%"
				}
				if(this.integrated){
					return `0 ${radius} ${radius} 0`
				}
				return radius
			},
			reduceLimit(){
				return Number(this.inValue)<=this.setting.min
			},
			addLimit(){
				return Number(this.inValue)>=this.setting.max
			},
			iconSetting(){
				if(this.$api.isObject(this.icon)){
					return Object.assign({},this.dicIcon,this.icon)
				}
				return this.dicIcon
			}
		},
		methods: {
			emitEvent(){
				this.$emit("change", Number(this.inValue));
				this.$emit("input", Number(this.inValue));
			},
			//设置数值小数位，用于最终显示
			setDecimals(value){
				//当前数值小数位
				let dLen = this.$api.lengthOf(value, {type:"decimal"});
				let allowLen = this.decimals ||0;
				let fixLen = this.fixDecimals ||0;
				//修正数值
				if(dLen > allowLen){
					value = this.$api.numberFix(Number(value), allowLen);
					dLen = allowLen;
				}
				//修正显示
				if(dLen>=fixLen){
					return value.toString()
				}else{
					return value.toFixed(fixLen)
				}
			},
			//切换input控件
			openInput(){
				if(this.setting.useInput){
					this.inputFocus = true;
				}
			},
			actionFocus(){
				this.oValue = this.inValue;
				if(!this.inValue){
					this.oValue = this.inValue = "0"
				}
			},
			actionBlur(e){
				let val = Number(e.detail.value);
				if (isNaN(val)) {
					this.inValue = NaN;
					this.$nextTick(()=>{
						this.inValue = this.oValue;
					})
					return
				}
				//原生组件存在数据脱节问题，反复输入错误数据会出现显示值与存储值不一致
				this.inValue = NaN;
				this.$nextTick(()=>{//进行组件数据同步。可能有兼容问题
					let newVal = this.setDecimals(val),
						max = this.setting.max,
						min = this.setting.min;
					if(newVal>max){
						this.inValue = String(max)
					}else if(newVal<min){
						this.inValue = String(min)
					}else{
						this.inValue = newVal
					}
					this.emitEvent();
					this.inputFocus = false;
				})
			},
			actionConfirm(){
				
			},
			actionInput(e){
				this.inValue = this.setDecimals(e.detail.value)
			},
			add(stopEvent){
				if(this.disabled||this.readOnly||this.addLimit){
					return
				}
				let max = this.setting.max,
					min = this.setting.min,
					inValue = Number(this.inValue),
					nextVal = this.setDecimals(inValue + this.setting.step);
				this.inValue = String(this.adjust(nextVal));
				if(!stopEvent){
					this.emitEvent()
				}
				this.$forceUpdate();
			},
			reduce(stopEvent){
				if(this.disabled||this.readOnly||this.reduceLimit){
					return
				}
				let max = this.setting.max,
					min = this.setting.min,
					inValue = Number(this.inValue),
					nextVal = this.setDecimals(inValue - this.setting.step);
				this.inValue = String(this.adjust(nextVal));
				if(!stopEvent){
					this.emitEvent()
				}
				this.$forceUpdate();
			},
			//长按增减
			touchStart(e, type){
				if (e.touches.length != 1) {
					return false;
				}
				if(this.useLongPress){
					this.longPressTimer = setTimeout(()=>{
						this.longPress = true;
						this.increaseTimer = setInterval(()=>{
							this[type](true);
						}, this.increaseSpeed);
					}, this.longPressDuration)
				}
			},
			touchMove(e, type){
				if(this.longPressTimer){
					clearTimeout(this.longPressTimer);
					this.longPressTimer =null;
				}
				if(this.increaseTimer){
					clearInterval(this.increaseTimer);
					this.increaseTimer =null;
				}
			},
			touchEnd(e, type){
				if(this.longPressTimer){
					clearTimeout(this.longPressTimer);
					this.longPressTimer =null;
				}
				if(this.increaseTimer){
					clearInterval(this.increaseTimer);
					this.increaseTimer =null;
				}
				//已激活长按则不触发点击
				if(this.longPress){
					this.longPress = false;
					this.emitEvent();	//只在结束抛出事件
					return
				}
				//触发点击
				this[type]();
			},
			adjust(val){//如果max小于min，则校验失效，相当于max=min=Infinity
				let max = this.setting.max,
					min = this.setting.min;
				val = Number(val) ||0;
				let res = val;
				if(max>=min && val>max){
					res = max
				}else if(max>=min && val<min){
					res = min
				}
				return res
			}
		},
		watch:{
			//监听外部值变更
			"value":{
				handler(newVal){
					if(this.inValue!=newVal){
						newVal = this.setDecimals(newVal||0);
						this.inValue = String(this.adjust(newVal));
					}
				}
			},
		},
		created() {
			if(this.setting.zeroShow||this.value){//是否设置初始值
				this.inValue = this.setDecimals(this.value)
			}
		}
	}
</script>

<style lang="scss">
.vi_stepper{
	display: inline-flex;
	line-height: 1;
	.input{
		font-size: inherit;
	}
	.vi_stepper_box{
		position: relative;
		margin-left: .25em;
		margin-right: .25em;
		.vi_stepper_inputbox{
			width: 100%;
			height: 100%;
			.vi_inputCell{
				position: absolute;
				width: 100%;
				height: 100%;
				left: 0;
				top: 0;
				display: flex;
				align-items: center;
				justify-content: center;
				text-align: center;
			}
		}
		.vi_stepper_show{
			opacity: 1;
			z-index: 2;
		}
		.vi_stepper_hide{
			opacity: 0;
			z-index: 1;
		}
	}
	&.vi_stepper_integrated.vi_stepper_borderMode{
		.vi_stepper_box{
			margin-left: 0;
			margin-right: 0;
		}
	}
}
</style>
